#include "headers/CGridSceneNode.h"
#include <cmath>
#include <ISceneNode.h>

using namespace irr;

CGridSceneNode::SGrid::SGrid()
: IsVisible(true)
, Alignment(CENTER)
, Spacing(8.f)
, Size(2048.f, 2048.f)
, Offset(0.f, 0.f, 0.f)
, GridColor(irr::video::SColor(255, 128, 128, 128))
, MaxRenderDist(FLT_MAX)
, GridDirty(true)
, BoundingBoxDirty(true)
, MeshBuffer(0) {
    // Set the material
    MeshBuffer = new irr::scene::CDynamicMeshBuffer(irr::video::EVT_STANDARD, irr::video::EIT_16BIT);
    setDefaultMaterial();
}

CGridSceneNode::SGrid::~SGrid() {
    if (MeshBuffer)
        MeshBuffer->drop();
}

CGridSceneNode::SGrid::SGrid(const SGrid& other)
: MeshBuffer(0) {
    MeshBuffer = new irr::scene::CDynamicMeshBuffer(irr::video::EVT_STANDARD, irr::video::EIT_16BIT);
    *this = other;
}

CGridSceneNode::SGrid& CGridSceneNode::SGrid::operator=(const CGridSceneNode::SGrid& other) {
    if (&other == this)
        return *this;

    IsVisible = other.IsVisible;
    Alignment = other.Alignment;
    Spacing = other.Spacing;
    Size = other.Size;
    Offset = other.Offset;
    GridColor = other.GridColor;
    MaxRenderDist = other.MaxRenderDist;
    setMaterial(other.getMaterial());
    // note, each object keeps it's own MeshBuffer for the lifetime and just changes the content
    GridDirty = true;

    return *this;
}

void CGridSceneNode::SGrid::setDefaultMaterial() {
    if (!MeshBuffer)
        return;
    MeshBuffer->getMaterial().Wireframe = false;
    MeshBuffer->getMaterial().Lighting = false;
    MeshBuffer->getMaterial().Thickness = 1;
    MeshBuffer->getMaterial().FogEnable = false;
    MeshBuffer->getMaterial().ZWriteEnable = true;
    MeshBuffer->getMaterial().ZBuffer = true;
    MeshBuffer->getMaterial().BackfaceCulling = true;
    MeshBuffer->getMaterial().AntiAliasing = false;
}

bool CGridSceneNode::SGrid::canUseGridLine(irr::f32 pos, bool axisX, const CGridSceneNode * const gridNode) {
    if (!gridNode)
        return true;

    const irr::f32 epsilonHalf = 0.1f;
    for (irr::u32 i = 0; i < gridNode->getNumberOfGrids(); ++i) {
        const SGrid & otherGrid = gridNode->getGrid(i);
        if (&otherGrid == this)
            continue;
        if (otherGrid.getSpacing() <= getSpacing())
            continue; // we want the biggest spacing to show up most
        if (!otherGrid.isVisible())
            continue;

        irr::core::vector2df otherLeftTop, otherRightBottom;
        otherGrid.calcGridInside(otherLeftTop, otherRightBottom);
        irr::f32 dist = axisX ? irr::core::abs_(pos - otherLeftTop.X) : irr::core::abs_(pos - otherLeftTop.Y);
        irr::f32 mod = fmod(dist + epsilonHalf, otherGrid.getSpacing());
        if (irr::core::equals(mod, 0.f, 2 * epsilonHalf))
            return false;
    }
    return true;
}

void CGridSceneNode::SGrid::calcGridInside(irr::core::vector2df& leftTop, irr::core::vector2df& rightBottom) const {
    leftTop = irr::core::vector2df(0, 0);
    rightBottom = leftTop;

    //Set our corners
    if (Alignment == CENTER) {
        rightBottom.X = Spacing * (int) (0.5f * Size.Width / Spacing);
        rightBottom.Y = Spacing * (int) (0.5f * Size.Height / Spacing);
        leftTop.X = -rightBottom.X;
        leftTop.Y = -rightBottom.Y;
    } else {
        leftTop.X = -Size.Width / 2.f;
        leftTop.Y = -Size.Height / 2.f;
        rightBottom.X = leftTop.X + Spacing * (int) (Size.Width / Spacing);
        rightBottom.Y = leftTop.Y + Spacing * (int) (Size.Height / Spacing);
    }
    leftTop.X += Offset.X;
    leftTop.Y += Offset.Z;
    rightBottom.X += Offset.X;
    rightBottom.Y += Offset.Z;
}

void CGridSceneNode::SGrid::regenerateMesh(const CGridSceneNode * const gridNode) {
    GridDirty = false;

    //Clean up memory
    MeshBuffer->getIndexBuffer().set_used(0);
    MeshBuffer->getVertexBuffer().set_used(0);

    u32 numVertices = ((u32) (Size.Width / Spacing) + 1) * 2 + ((u32) (Size.Height / Spacing) + 1) * 2;
    if (numVertices > 65535) {
        //Too many vertices on 16 bit for for 16bit indices of SMeshBuffer
        //Returning with a blank buffer to avoid segfaulting the entire application
        return;
    }

    if (Spacing <= 0)
        return;

    // inner has the last outside borders, while outer has the full size
    core::vector2df innerLeftTop, innerRightBottom;
    calcGridInside(innerLeftTop, innerRightBottom);
    core::vector3df outerLeftTop(-Size.Width / 2.f, 0, -Size.Height / 2.f);
    core::vector3df outerRightBottom(Size.Width / 2.f, 0, Size.Height / 2.f);
    outerLeftTop += Offset;
    outerRightBottom += Offset;

    u32 indexIndex = 0;

    //lines parallel to X-axis 
    core::vector3df start = outerLeftTop;
    core::vector3df end = outerRightBottom;
    for (f32 x = 0.f; x <= (innerRightBottom.X - innerLeftTop.X); x += Spacing) {
        start.X = x + innerLeftTop.X;

        if (!canUseGridLine(start.X, true, gridNode))
            continue;

        end.X = start.X;

        MeshBuffer->getVertexBuffer().push_back(video::S3DVertex(start, core::vector3df(0, 1, 0), GridColor, core::vector2df(0.0f, 0.0f)));
        MeshBuffer->getVertexBuffer().push_back(video::S3DVertex(end, core::vector3df(0, 1, 0), GridColor, core::vector2df(0.0f, 0.0f)));

        MeshBuffer->getIndexBuffer().push_back(indexIndex++);
        MeshBuffer->getIndexBuffer().push_back(indexIndex++);
    }

    //lines parallel to Z-axis
    start = outerLeftTop;
    end = outerRightBottom;
    for (f32 z = 0.f; z <= (innerRightBottom.Y - innerLeftTop.Y); z += Spacing) {
        start.Z = z + innerLeftTop.Y;

        if (!canUseGridLine(start.Z, false, gridNode))
            continue;

        end.Z = start.Z;

        MeshBuffer->getVertexBuffer().push_back(video::S3DVertex(start, core::vector3df(0, 1, 0), GridColor, core::vector2df(0.0f, 0.0f)));
        MeshBuffer->getVertexBuffer().push_back(video::S3DVertex(end, core::vector3df(0, 1, 0), GridColor, core::vector2df(0.0f, 0.0f)));

        MeshBuffer->getIndexBuffer().push_back(indexIndex++);
        MeshBuffer->getIndexBuffer().push_back(indexIndex++);
    }

    // Create our box, it is the size of the grid exactly, plus 1 in the Y axis
    irr::core::aabbox3df bbox(-(f32) Size.Width / 2.f, -0.5f, -(f32) Size.Height / 2.f, (f32) Size.Width / 2.f, 0.5f, (f32) Size.Height / 2.f);
    bbox.MinEdge += Offset;
    bbox.MaxEdge += Offset;
    MeshBuffer->setBoundingBox(bbox);

    BoundingBoxDirty = true;
}

void CGridSceneNode::SGrid::setVisible(bool visible) {
    IsVisible = visible;
    GridDirty = true; // grids must be recalculated because they don't have lines used by another visible grid
}

bool CGridSceneNode::SGrid::isVisible() const {
    return IsVisible;
}

void CGridSceneNode::SGrid::setAlignment(CGridSceneNode::EAlign align) {
    Alignment = align;
    GridDirty = true;
}

CGridSceneNode::EAlign CGridSceneNode::SGrid::getAlignment() const {
    return Alignment;
}

void CGridSceneNode::SGrid::setSpacing(f32 newspacing) {
    Spacing = newspacing;
    GridDirty = true;
}

f32 CGridSceneNode::SGrid::getSpacing() const {
    return Spacing;
}

void CGridSceneNode::SGrid::setSize(const irr::core::dimension2df& newsize) {
    Size = newsize;
    GridDirty = true;
}

const irr::core::dimension2df& CGridSceneNode::SGrid::getSize() const {
    return Size;
}

void CGridSceneNode::SGrid::setOffset(const irr::core::vector3df& offset) {
    Offset = offset;
    GridDirty = true;
}

const irr::core::vector3df& CGridSceneNode::SGrid::getOffset() const {
    return Offset;
}

void CGridSceneNode::SGrid::setGridColor(video::SColor newcolor) {
    GridColor = newcolor;
    GridDirty = true;
}

video::SColor CGridSceneNode::SGrid::getGridColor() const {
    return GridColor;
}

void CGridSceneNode::SGrid::setMaxRenderDistance(irr::f32 dist) {
    MaxRenderDist = dist;
}

irr::f32 CGridSceneNode::SGrid::getMaxRenderDistance() const {
    return MaxRenderDist;
}

void CGridSceneNode::SGrid::setMaterial(const video::SMaterial& newMaterial) {
    MeshBuffer->getMaterial() = newMaterial;
}

const irr::video::SMaterial& CGridSceneNode::SGrid::getMaterial() const {
    return MeshBuffer->getMaterial();
}

irr::video::SMaterial& CGridSceneNode::SGrid::getMaterial() {
    return MeshBuffer->getMaterial();
}

void CGridSceneNode::SGrid::getGridLines(irr::core::array<irr::core::line2df> &lines, const irr::core::matrix4* transformation) const {
    video::S3DVertex* vertices = MeshBuffer->getVertexBuffer().pointer();
    for (u32 i = 0; i < MeshBuffer->getVertexBuffer().size() / 2; ++i) {
        core::vector3df vs = vertices[i * 2].Pos;
        core::vector3df ve = vertices[i * 2 + 1].Pos;

        if (transformation) {
            transformation->transformVect(vs);
            transformation->transformVect(ve);
        }

        lines.push_back(core::line2df(vs.X, vs.Z, ve.X, ve.Z));
    }
}

const irr::core::aabbox3d<irr::f32>& CGridSceneNode::SGrid::getBoundingBox() const {
    return MeshBuffer->getBoundingBox();
}

void CGridSceneNode::SGrid::render(irr::video::IVideoDriver * driver) {
    if (!isVisible())
        return;

    driver->setMaterial(MeshBuffer->getMaterial());

    driver->drawVertexPrimitiveList(MeshBuffer->getVertexBuffer().getData(), // const void* vertices
            MeshBuffer->getVertexBuffer().size(), // u32 vertexCount
            MeshBuffer->getIndexBuffer().getData(), // const void* indexList
            MeshBuffer->getVertexBuffer().size() / 2, // u32 primCount
            MeshBuffer->getVertexType(), // E_VERTEX_TYPE vType
            scene::EPT_LINES, // scene::E_PRIMITIVE_TYPE pType
            MeshBuffer->getIndexType()); // E_INDEX_TYPE iType
}


// ----------------------------------------------------------------------------

CGridSceneNode::CGridSceneNode(ISceneNode* parent, scene::ISceneManager* smgr, s32 id, irr::u32 numGrids)
: scene::ISceneNode(parent, smgr, id), SelectionMesh(0), SelectionMeshBuffer(0), ShowSelectionOnce(false) {
#ifdef _DEBUG
    setDebugName("CGridSceneNode");
#endif
    // Set the default culling state to Frustum Box
    AutomaticCullingState = scene::EAC_FRUSTUM_BOX;

    for (irr::u32 i = 0; i < numGrids; ++i) {
        Grids.push_back(SGrid());
    }

    SelectionMeshBuffer = new irr::scene::CDynamicMeshBuffer(irr::video::EVT_STANDARD, irr::video::EIT_16BIT);
    SelectionMesh = new irr::scene::SMesh();
    SelectionMesh->addMeshBuffer(SelectionMeshBuffer);
    SelectionMeshBuffer->getMaterial().Wireframe = false;
    SelectionMeshBuffer->getMaterial().Lighting = false;
    SelectionMeshBuffer->getMaterial().FogEnable = false;
    SelectionMeshBuffer->getMaterial().BackfaceCulling = false;
}

CGridSceneNode::~CGridSceneNode() {
    if (SelectionMesh)
        SelectionMesh->drop();
    if (SelectionMeshBuffer)
        SelectionMeshBuffer->drop();
}

CGridSceneNode* CGridSceneNode::clone(scene::ISceneNode *newParent, scene::ISceneManager *newSceneManager) {
    if (!newParent) newParent = Parent;
    if (!newSceneManager) newSceneManager = SceneManager;

    CGridSceneNode* clone = new CGridSceneNode(
            Parent,
            SceneManager,
            ID,
            Grids.size());
    if (!clone)
        return 0;

    for (irr::u32 i = 0; i < Grids.size(); ++i) {
        SGrid & grid = clone->getGrid(i);
        grid = getGrid(i);
    }

    clone->drop();
    return clone;
}

void CGridSceneNode::OnRegisterSceneNode() {
    if (hasDirtyGrid())
        regenerateMeshes();
    if (hasDirtyBoundingBox()) {
        rebuildBoundingBox();
        rebuildSelectionMesh();
    }

    if (IsVisible)
        SceneManager->registerNodeForRendering(this);

    ISceneNode::OnRegisterSceneNode();
}

void CGridSceneNode::setGridVisibilityByDistToCamera() {
    for (irr::u32 i = 0; i < Grids.size(); ++i) {
        Grids[i].setVisible(true);

        if (!SceneManager->getActiveCamera()->isOrthogonal()) {
            irr::core::plane3df gridPlane(0, Grids[i].getOffset().Y, 0, 0, 1, 0);
            AbsoluteTransformation.transformPlane(gridPlane);
            irr::f32 distToCam = gridPlane.getDistanceTo(SceneManager->getActiveCamera()->getAbsolutePosition());
            if (!SceneManager->getActiveCamera()->isOrthogonal() && distToCam > Grids[i].getMaxRenderDistance()) {
                Grids[i].setVisible(false);
            }
        }
    }
}

void CGridSceneNode::render() {
    video::IVideoDriver* driver = SceneManager->getVideoDriver();

    if (!driver)
        return;

    //	setGridVisibilityByDistToCamera();

    driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
    for (irr::u32 i = 0; i < Grids.size(); ++i) {
        Grids[i].render(driver);
    }

    if (ShowSelectionOnce) {
        ShowSelectionOnce = false;
        driver->setMaterial(SelectionMeshBuffer->getMaterial());

        driver->drawVertexPrimitiveList(SelectionMeshBuffer->getVertexBuffer().getData(), // const void* vertices
                SelectionMeshBuffer->getVertexBuffer().size(), // u32 vertexCount
                SelectionMeshBuffer->getIndexBuffer().getData(), // const void* indexList
                SelectionMeshBuffer->getIndexBuffer().size() / 3, // u32 primCount
                SelectionMeshBuffer->getVertexType(), // E_VERTEX_TYPE vType
                scene::EPT_TRIANGLES, // scene::E_PRIMITIVE_TYPE pType
                SelectionMeshBuffer->getIndexType()); // E_INDEX_TYPE iType
    }
}

bool CGridSceneNode::hasDirtyBoundingBox() const {
    bool hasDirty = false;
    for (irr::u32 i = 0; i < Grids.size(); ++i) {
        if (Grids[i].BoundingBoxDirty) {
            hasDirty = true;
            break;
        }
    }
    return hasDirty;
}

bool CGridSceneNode::hasDirtyGrid() const {
    bool hasDirty = false;
    for (irr::u32 i = 0; i < Grids.size(); ++i) {
        if (Grids[i].GridDirty) {
            hasDirty = true;
            break;
        }
    }
    return hasDirty;
}

void CGridSceneNode::regenerateMeshes() {
    for (irr::u32 i = 0; i < Grids.size(); ++i) {
        Grids[i].regenerateMesh(this);
    }
}

void CGridSceneNode::rebuildBoundingBox() {
    if (Grids.empty())
        BoundingBox.reset(irr::core::vector3df(FLT_MAX, FLT_MAX, FLT_MAX));
    else {
        // recalculate when one boundingbox has changed
        bool hasDirty = false;
        for (irr::u32 i = 0; i < Grids.size(); ++i) {
            if (Grids[i].BoundingBoxDirty) {
                hasDirty = true;
                break;
            }
        }

        if (hasDirty) {
            BoundingBox = Grids[0].getBoundingBox();
            Grids[0].BoundingBoxDirty = false;
            for (irr::u32 i = 1; i < Grids.size(); ++i) {
                BoundingBox.addInternalBox(Grids[i].getBoundingBox());
                Grids[i].BoundingBoxDirty = false;
            }
        }
    }
}

void CGridSceneNode::rebuildSelectionMesh() {
    SelectionMeshBuffer->getVertexBuffer().reallocate(4);
    SelectionMeshBuffer->getVertexBuffer().set_used(0);

    irr::video::SColor col(128, 0, 0, 255);
    irr::core::vector3df edges[8];
    BoundingBox.getEdges(edges);
    irr::video::S3DVertex* vertices = SelectionMeshBuffer->getVertexBuffer().pointer();
    for (int i = 0; i < 4; ++i)
        SelectionMeshBuffer->getVertexBuffer().push_back(video::S3DVertex((edges[i * 2] + edges[i * 2 + 1]) / 2, core::vector3df(0, 1, 0), col, core::vector2df(0.0f, 0.0f)));

    if (SelectionMeshBuffer->getIndexBuffer().size() < 6) {
        SelectionMeshBuffer->getIndexBuffer().reallocate(6);
        SelectionMeshBuffer->getIndexBuffer().set_used(0);
        SelectionMeshBuffer->getIndexBuffer().push_back(0);
        SelectionMeshBuffer->getIndexBuffer().push_back(1);
        SelectionMeshBuffer->getIndexBuffer().push_back(2);
        SelectionMeshBuffer->getIndexBuffer().push_back(2);
        SelectionMeshBuffer->getIndexBuffer().push_back(1);
        SelectionMeshBuffer->getIndexBuffer().push_back(3);
    }

    SelectionMeshBuffer->setDirty();

    irr::scene::ITriangleSelector * s = SceneManager->createTriangleSelector(SelectionMesh, this);
    setTriangleSelector(s);
    s->drop();
}

const core::aabbox3d<f32>& CGridSceneNode::getBoundingBox() const {
    return BoundingBox;
}

u32 CGridSceneNode::getMaterialCount() const {
    return Grids.size();
}

video::SMaterial& CGridSceneNode::getMaterial(u32 i) {
    return Grids[i].getMaterial();
}

irr::u32 CGridSceneNode::getNumberOfGrids() const {
    return Grids.size();
}

irr::u32 CGridSceneNode::addGrid() {
    Grids.push_back(SGrid());
    return Grids.size() - 1;
}

void CGridSceneNode::removeGrid(irr::u32 index) {
    Grids.erase(index, 1);
}

CGridSceneNode::SGrid& CGridSceneNode::getGrid(irr::u32 index) {
    return Grids[index];
}

const CGridSceneNode::SGrid& CGridSceneNode::getGrid(irr::u32 index) const {
    return Grids[index];
}

void CGridSceneNode::setGridsSize(const irr::core::dimension2df& newsize) {
    GridsSize = newsize;
    for (irr::u32 i = 0; i < Grids.size(); ++i) {
        Grids[i].setSize(newsize);
    }
}

const irr::core::dimension2df& CGridSceneNode::getGridsSize() const {
    return GridsSize;
}
